/**
 * This file implements very simple DMA for sosh.
 *
 * It does not free and only keeps a memory pool
 * big enough to get the network drivers booted.
 */
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#include <sel4/types.h>
#include <cspace/cspace.h>
#include <mapping.h>
#include <ut_manager/ut.h>
#include <vmem_layout.h>

#define verbose 0
#include <sys/debug.h>
#include <sys/panic.h>

#define DMA_SIZE     (_dma_physical_end - _dma_physical_start)
#define DMA_PAGES    (DMA_SIZE >> seL4_PageBits)
#define DMA_VEND     (DMA_VSTART + DMA_SIZE)

/* prototypes for the functions we expose */
extern void sos_flush(seL4_Word frame_vaddr, seL4_Word size);
extern void *sos_dma_malloc(uint32_t size);
extern void sos_dma_free(void *ptr);
extern void *sos_phys_to_virt(void *addr);
extern void *sos_virt_to_phys(void *addr);


static seL4_CPtr* _dma_frame_caps;

static seL4_Word _dma_physical_start = 0;
static seL4_Word _dma_physical_end = 0;
static seL4_Word _next_vaddr = 0;


static inline void _dma_init_page(seL4_Word paddr, seL4_Word vaddr, int index){
    seL4_Word type = seL4_ARM_SmallPageObject;
    seL4_Word size = seL4_PageBits;
    seL4_CPtr p;
    int err;

    
    /* Create the frame cap */
    err =  cspace_ut_retype_addr(paddr,
                                 type,
                                 size,
                                 cur_cspace,
                                 &p);
    assert(!err);
    _dma_frame_caps[index] = p;

    /* Map in the frame */
    err = map_page(p, seL4_CapInitThreadPD, vaddr, seL4_AllRights, 
                   seL4_ARM_Default_VMAttributes);
    assert(!err);

}


int dma_init(seL4_Word dma_paddr_start, int sizebits){
    seL4_Word paddr;
    seL4_Word vaddr;
    int i;

    assert(_dma_physical_start == 0);

    /* Allocate contiguous memory */
    _dma_physical_start = dma_paddr_start;
    _dma_physical_end = dma_paddr_start + (1 << sizebits);
    conditional_panic(!_dma_physical_start, 
                      "Failed to initialise DMA memory region!");

    /* Allocate space for cap list used by dma_flush */
    _dma_frame_caps = (seL4_CPtr*)malloc(sizeof(seL4_CPtr)* DMA_PAGES);
    conditional_panic(!_dma_frame_caps, "Not enough heap space for dma frame caps");

    /* Convert to frames and map in */
    paddr = _dma_physical_start;
    vaddr = DMA_VSTART; 
    for(i = 0; i < DMA_PAGES; i++){

        _dma_init_page(paddr, vaddr, i);

        paddr += (1 << seL4_PageBits);
        vaddr += (1 << seL4_PageBits);
    }

    _next_vaddr = DMA_VSTART;
    printf("DMA memory is at 0x%x to 0x%x\n", DMA_VSTART, DMA_VEND);

    return 0;
}

void *dma_morecore(unsigned int size) {
    assert(_dma_physical_start);
    conditional_panic(_next_vaddr >= DMA_VEND, 
        "DMA memory region has run out!");

    seL4_Word vaddr = _next_vaddr;
    _next_vaddr += size;

    return (void *) vaddr;
}

void *sos_dma_malloc(uint32_t size) {
    return dma_morecore(size);
}

void sos_dma_free(void * ptr) {
    /* We do not allow free */
}

/**
 * Flushes and invalidates any physical frames mapped to an address within the region [vaddr, vaddr+size).
 *
 * Only for use on memory allocated with sos_dma_malloc.
 */
void sos_flush(seL4_Word vaddr, seL4_Word size) {
    int start, pages, i;
    int err;

    conditional_panic(vaddr < DMA_VSTART || vaddr >= DMA_VEND,
            "sos_flush called on invalid memory address!");

    /* page align vaddr */
    size += vaddr & ((1 << seL4_PageBits) - 1);
    vaddr &= ~((1 << seL4_PageBits) - 1);

    /* calculate span of pages */
    start = (vaddr - DMA_VSTART) >> seL4_PageBits;
    pages = (size + (1 << seL4_PageBits) - 1) >> seL4_PageBits;

    for(i = start; i < start + pages; i++){
        err = seL4_ARM_Page_FlushCaches(_dma_frame_caps[i]);
        assert(!err || !"Failed to flush DMA page");
    }

}

extern void *sos_phys_to_virt(void *_paddr) {
    seL4_Word paddr = (seL4_Word) _paddr;
    conditional_panic(paddr <  _dma_physical_start || 
                      paddr >= _dma_physical_start + (1 << (DMA_SIZE_BITS)),
            "sos_phys_to_virt called on invalid memory address!");
    return (void*)(paddr - _dma_physical_start + DMA_VSTART);
}

void *sos_virt_to_phys(void *_vaddr) {
    seL4_Word vaddr = (seL4_Word)_vaddr;
    conditional_panic(vaddr < DMA_VSTART || vaddr >= DMA_VEND, 
            "sos_virt_to_phys called on invalid memory address!");
    return (void*)(vaddr - DMA_VSTART + _dma_physical_start);
}

